package com.atguigu.gulimall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.to.SkuStockTo;
import com.atguigu.common.to.es.EsSkuUpTo;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.search.config.EsConfig;
import com.atguigu.gulimall.search.constant.IndexConstant;
import com.atguigu.gulimall.search.feign.ProductFeignService;
import com.atguigu.gulimall.search.service.MallSearchService;
import com.atguigu.gulimall.search.vo.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author zhuguanming
 * @description search服务
 * @date 2021/5/31 21:37
 */
@Service
@Slf4j
public class MallSearchServiceImpl implements MallSearchService {

    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @Autowired
    private ProductFeignService productFeignService;
    private final String SEPARATOR = "_";
    private final String BASE_URL = "http://search.gulimall.com/list.html?";

    @Override
    public SearchResult search(SearchParam param) {
        SearchResult searchResult = null;
        try {
            SearchResponse searchResponse = restHighLevelClient.search(getSearchRequestBuilder(param), EsConfig.COMMON_OPTIONS);
            searchResult = handleResponse(searchResponse, param);
            log.info("searchResult ~~~ ：{}", JSON.toJSONString(searchResult));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return searchResult;
    }

    private SearchResult handleResponse(SearchResponse searchResponse, SearchParam param) throws IOException {
        SearchResult searchResult = new SearchResult();
        searchResult.setPageNum(param.getPageNum());
        searchResult.setTotal(searchResponse.getHits().getTotalHits().value);
        searchResult.setTotalPages(Integer.valueOf(String.valueOf(Math.ceil(searchResult.getTotal() / (float) IndexConstant.PRODUCT_PAGE_SIZE))));
        List<SearchResult.NavVo> navVos = new ArrayList<>();
        List<Long> attrIds = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(param.getAttrs())) {
            param.getAttrs().forEach(item -> {
                if (StringUtils.isNotBlank(item)) {
                    String[] attrSplit = item.split(SEPARATOR);
                    if (attrSplit.length > 1) {
                        R attrInfo = productFeignService.info(Long.parseLong(attrSplit[0]));
                        if (attrInfo.getCode() == 0) {
                            try {
                                AttrVoNavVo attrVoNavVo = (AttrVoNavVo) attrInfo.getData(new TypeReference<AttrVoNavVo>() {}, "attr");
                                String toLinkStr = replaceQueryString(param.getQueryString(), item, "attrs");
                                navVos.add(new SearchResult.NavVo(attrVoNavVo.getAttrName(), attrSplit[1], BASE_URL + toLinkStr));
                            } catch (Exception e) {
                                log.error("面包屑处理异常：{}", e.getMessage());
                            }
                        }
                    }
                    attrIds.add(Long.parseLong(attrSplit[0]));
                }
            });
        }
        if (CollectionUtils.isNotEmpty(param.getBrandId())) {
            R barangQueryR = productFeignService.listByIds(param.getBrandId());
            if (barangQueryR.getCode() == 0) {
                ObjectMapper objectMapper = new ObjectMapper();
                List<BrandVo> brandVoList = objectMapper.readValue(JSON.toJSONString(barangQueryR.getData()), objectMapper.getTypeFactory().constructCollectionType(List.class, BrandVo.class));
                String toLinkStr = replaceQueryString(param.getQueryString(), String.valueOf(param.getBrandId().get(0)), "brandId");
                navVos.add(new SearchResult.NavVo("品牌", brandVoList.get(0).getName(), BASE_URL + toLinkStr));
            }
        }
        if (param.getCatalog3Id() != null) {
            R categoryR = productFeignService.catalogInfo(param.getCatalog3Id());
            if (categoryR.getCode() == 0) {
                CategoryVo categoryVo = (CategoryVo) categoryR.getData(new TypeReference<CategoryVo>(){},"data");
                String toLinkStr = replaceQueryString(param.getQueryString(), String.valueOf(param.getCatalog3Id()), "catalog3Id");
                navVos.add(new SearchResult.NavVo("分类", categoryVo.getName(), BASE_URL + toLinkStr));
            }
        }
        searchResult.setAttrIds(attrIds);
        searchResult.setNavVos(navVos);
        List<Integer> pageNavs = new ArrayList<>();
        for (Integer i = 1; i <= searchResult.getTotalPages(); i++) {
            pageNavs.add(i);
        }
        searchResult.setPageNavs(pageNavs);
        SearchHit[] hits = searchResponse.getHits().getHits();
        List<EsSkuUpTo> esSkuList = new ArrayList<>();
        if (hits != null && hits.length > 0) {
            for (SearchHit hit : hits) {
                EsSkuUpTo esSku = JSON.parseObject(hit.getSourceAsString(), EsSkuUpTo.class);
                if (StringUtils.isNotBlank(param.getKeyword())) {
                    HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
                    esSku.setSkuTitle(skuTitle.getFragments()[0].string());
                }
                esSkuList.add(esSku);
            }
        }
        searchResult.setProducts(esSkuList);
        Aggregations aggregations = searchResponse.getAggregations();
        List<SearchResult.BrandVo> brandVoList = new ArrayList<>();
        ParsedLongTerms barndAgg = aggregations.get("barnd_agg");
        for (Terms.Bucket bucket : barndAgg.getBuckets()) {
            ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brand_name_agg");
            ParsedStringTerms brandImageAgg = bucket.getAggregations().get("barnd_image_agg");
            String brandImage = brandImageAgg.getBuckets().get(0).getKeyAsString();
            String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
            brandVoList.add(new SearchResult.BrandVo(Long.valueOf(bucket.getKeyAsString()), brandName, brandImage));
        }
        searchResult.setBrands(brandVoList);
        List<SearchResult.CatalogVo> catalogVoList = new ArrayList<>();
        ParsedLongTerms catalogAgg = aggregations.get("catalog_agg");
        for (Terms.Bucket bucket : catalogAgg.getBuckets()) {
            String catalogName = ((ParsedStringTerms) bucket.getAggregations().get("catalog_name_agg")).getBuckets().get(0).getKeyAsString();
            catalogVoList.add(new SearchResult.CatalogVo(Long.valueOf(bucket.getKeyAsString()), catalogName));
        }
        searchResult.setCatalogs(catalogVoList);
        List<SearchResult.AttrVo> attrVoList = new ArrayList<>();
        ParsedNested attrAgg = aggregations.get("attr_agg");
        ParsedLongTerms attrsAttrIdAgg = attrAgg.getAggregations().get("attrs_attrId");
        for (Terms.Bucket bucket : attrsAttrIdAgg.getBuckets()) {
            String attrName = ((ParsedStringTerms) bucket.getAggregations().get("attrs_attrName")).getBuckets().get(0).getKeyAsString();
            List<String> attrValues = ((ParsedStringTerms) bucket.getAggregations().get("attrs_attrValue")).getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
            attrVoList.add(new SearchResult.AttrVo(Long.valueOf(bucket.getKeyAsString()), attrName, attrValues));
        }
        searchResult.setAttrs(attrVoList);
        return searchResult;
    }

    /**
     * 路径替换
     *
     * @param queryString 请求路径值
     * @param item        请求参数属性值
     * @param key        要替换的属性名
     * @return java.lang.String
     */
    private String replaceQueryString(String queryString, String item, String key) throws UnsupportedEncodingException {
        String encode = URLEncoder.encode(item, "UTF-8");
        // 对于;的特殊处理
        // 编码后：浏览器是：; JAVA是：%3B
        String s = encode.replaceAll("%3B", ";");
        return queryString.replaceFirst("[&]*" + key + "=" + s, "");
    }

    private SearchRequest getSearchRequestBuilder(SearchParam param) {
        SearchRequest searchRequest = new SearchRequest(IndexConstant.PRODUCT_INDEX);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        if (StringUtils.isNotBlank(param.getKeyword())) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle", param.getKeyword()));
            //高亮
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle").preTags("<b style='color:red'>").postTags("</b>");
            searchSourceBuilder.highlighter(highlightBuilder);
        }
        //查询：keyword模糊匹配、过滤（不会计算得分、速度更快）（按照属性、分类、品牌、价格区间、库存）
        if (param.getCatalog3Id() != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId", param.getCatalog3Id()));
        }
        if (CollectionUtils.isNotEmpty(param.getBrandId())) {
            boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId", param.getBrandId()));
        }
        if (param.getHasStock() != null && param.getHasStock() == 1) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock", true));
        }else if(param.getHasStock() != null && param.getHasStock() == 0){
            boolQueryBuilder.filter(QueryBuilders.termsQuery("hasStock", false));
        }
        //TODO 价格的搜索有问题，es中存储的是keyword类型，并不是数值型
        if (StringUtils.isNotBlank(param.getSkuPrice())) {
            RangeQueryBuilder rangeQueryBuilder = new RangeQueryBuilder("skuPrice");
            String[] skuPriceArray = param.getSkuPrice().split(SEPARATOR);
            if (skuPriceArray.length >= 1 && StringUtils.isNotBlank(skuPriceArray[0])) {
                rangeQueryBuilder.gte(skuPriceArray[0]);
            }
            if (skuPriceArray.length > 1 && StringUtils.isNotBlank(skuPriceArray[1])) {
                rangeQueryBuilder.lte(skuPriceArray[1]);
            }
            boolQueryBuilder.filter(rangeQueryBuilder);
        }
        if (CollectionUtils.isNotEmpty(param.getAttrs())) {
            for (String attr : param.getAttrs()) {
                String[] attrIdValue = attr.split(SEPARATOR);
                String[] attrValues = attrIdValue[1].split(":");
                BoolQueryBuilder attrBoolQeury = QueryBuilders.boolQuery();
                attrBoolQeury.must(QueryBuilders.termsQuery("attrs.attrId", attrIdValue[0]));
                attrBoolQeury.must(QueryBuilders.termsQuery("attrs.attrValue", attrValues));
                boolQueryBuilder.filter(QueryBuilders.nestedQuery("attrs", attrBoolQeury, ScoreMode.None));
            }
        }
        searchSourceBuilder.query(boolQueryBuilder);
        //分页、排序
        if (StringUtils.isNotBlank(param.getSort())) {
            String sort = param.getSort();
            String[] sortNameRule = sort.split(SEPARATOR);
            searchSourceBuilder.sort(sortNameRule[0], SortOrder.valueOf(sortNameRule[1].toUpperCase()));
        }
        searchSourceBuilder.from((param.getPageNum() - 1) * IndexConstant.PRODUCT_PAGE_SIZE);
        searchSourceBuilder.size(IndexConstant.PRODUCT_PAGE_SIZE);
        //聚合分析
        TermsAggregationBuilder brandAgg = AggregationBuilders.terms("barnd_agg").field("brandId").size(20);
        brandAgg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));
        brandAgg.subAggregation(AggregationBuilders.terms("barnd_image_agg").field("brandImg").size(10));
        searchSourceBuilder.aggregation(brandAgg);
        TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(20);
        catalogAgg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
        searchSourceBuilder.aggregation(catalogAgg);
        NestedAggregationBuilder attrNestedAgg = AggregationBuilders.nested("attr_agg", "attrs");
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attrs_attrId").field("attrs.attrId").size(20);
        attrIdAgg.subAggregation(AggregationBuilders.terms("attrs_attrName").field("attrs.attrName").size(1));
        attrIdAgg.subAggregation(AggregationBuilders.terms("attrs_attrValue").field("attrs.attrValue").size(20));
        attrNestedAgg.subAggregation(attrIdAgg);
        searchSourceBuilder.aggregation(attrNestedAgg);
        log.info("searchSourceBuilder ~~~ ：{}", searchSourceBuilder);
        return searchRequest.source(searchSourceBuilder);
    }
}
